package cn.edu.hitsz.compiler.parser;

import cn.edu.hitsz.compiler.NotImplementedException;
import cn.edu.hitsz.compiler.ir.IRImmediate;
import cn.edu.hitsz.compiler.ir.IRValue;
import cn.edu.hitsz.compiler.ir.IRVariable;
import cn.edu.hitsz.compiler.ir.Instruction;
import cn.edu.hitsz.compiler.lexer.Token;
import cn.edu.hitsz.compiler.parser.table.Production;
import cn.edu.hitsz.compiler.parser.table.Status;
import cn.edu.hitsz.compiler.symtab.SymbolTable;
import cn.edu.hitsz.compiler.utils.FileUtils;
import org.w3c.dom.Attr;

import javax.management.Attribute;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

// TODO: 实验三: 实现 IR 生成

/**
 *
 */
public class IRGenerator implements ActionObserver {

    private final List<Instruction> IRList = new ArrayList<>();

    // 这里使用Attribute可以方便之后进行编译优化(Attribute中可以存值)
    // 还有一种方式是在whenShift时就将其转化为IRValue, 但是这样会使得whenReduce中强转或判断类型是否为IRVariable
    private final Stack<Attribute> attrs = new Stack<>();

    private IRValue attribute2IRValue(Attribute attribute) {
        String name = attribute.getName();
        if (name.matches("\\d+")) {
            return IRImmediate.of(Integer.parseInt(name));
        } else {
            return IRVariable.named(name);
        }
    }

    @Override
    public void whenShift(Status currentStatus, Token currentToken) {
        // TODO
        attrs.push(new Attribute(currentToken.getText(), null));
    }

    @Override
    public void whenReduce(Status currentStatus, Production production) {
        // TODO
        switch (production.index()) {
            case 6 -> { // S -> id = E;
                IRValue from = attribute2IRValue(attrs.pop());
                attrs.pop(); // =
                IRVariable result = IRVariable.named(attrs.pop().getName());
                IRList.add(Instruction.createMov(result, from));
                attrs.push(new Attribute(result.getName(), null));
            }
            case 7 -> { // S -> return E;
                IRList.add(Instruction.createRet(attribute2IRValue(attrs.pop())));
            }
            case 8 -> { // E -> E + A;
                IRVariable result = IRVariable.temp();
                IRValue rhs = attribute2IRValue(attrs.pop());
                attrs.pop(); // +
                IRValue lhs = attribute2IRValue(attrs.pop());
                attrs.push(new Attribute(result.getName(), null));
                IRList.add(Instruction.createAdd(result, lhs, rhs));
            }
            case 9 -> { // E -> E - A;
                IRVariable result = IRVariable.temp();
                IRValue rhs = attribute2IRValue(attrs.pop());
                attrs.pop(); // -
                IRValue lhs = attribute2IRValue(attrs.pop());
                attrs.push(new Attribute(result.getName(), null));
                IRList.add(Instruction.createSub(result, lhs, rhs));
            }
            case 11 -> { // A -> A * B;
                IRVariable result = IRVariable.temp();
                IRValue rhs = attribute2IRValue(attrs.pop());
                attrs.pop(); // *
                IRValue lhs = attribute2IRValue(attrs.pop());
                attrs.push(new Attribute(result.getName(), null));
                IRList.add(Instruction.createMul(result, lhs, rhs));
            }
            case 10, 12, 14, 15 -> {
                // E -> A;
                // A -> B;
                // B -> id;
                // B -> IntConst;
                // 同一位置 不用操作
            }
            case 13 -> { // B -> ( E );
                attrs.pop(); // )
                Attribute attr = attrs.pop(); // E
                attrs.pop(); // (
                attrs.push(attr);
            }
            default -> {
                for (int i = 0; i < production.body().size() - 1; i++) {
                    attrs.pop();
                }
            }
        }
    }


    @Override
    public void whenAccept(Status currentStatus) {
        // TODO
        // 没什么好做的
    }

    @Override
    public void setSymbolTable(SymbolTable table) {
        // TODO
        // 不太需要symbolTable
    }

    public List<Instruction> getIR() {
        // TODO
        return IRList;
    }

    public void dumpIR(String path) {
        FileUtils.writeLines(path, getIR().stream().map(Instruction::toString).toList());
    }
}

